/*
**********************************************************************
*                           The MIT License                          *
*                                                                    *
* Copyright (c) 2011 Flily Hsu                                       *
*                                                                    *
* Permission is hereby granted, free of charge, to any person        *
* obtaining a copy of this software and associated documentation     *
* files (the "Software"), to deal in the Software without            *
* restriction, including without limitation the rights to use, copy, *
* modify, merge, publish, distribute, sublicense, and/or sell copies *
* of the Software, and to permit persons to whom the Software is     *
* furnished to do so, subject to the following conditions:           *
*                                                                    *
* The above copyright notice and this permission notice shall be     *
* included in all copies or substantial portions of the Software.    *
*                                                                    *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,    *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND              *
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT        *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,       *
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER      *
* DEALINGS IN THE SOFTWARE.                                          *
**********************************************************************
*/
#ifndef ___BRAINFUCK_H___
#define ___BRAINFUCK_H___

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define KILOBYTES( n ) ( ( n ) * 1024 )
#define MEGABYTES( n ) ( ( n ) * 1024 * 1024 )
#define GIGABYTES( n ) ( ( n ) * 1024 * 1024 * 1024)

#define DEFAULT_STACK_SIZE		1024
#define DEFAULT_MEMORY_SIZE		MEGABYTES( 4 )

/* Brainfuck modes */
#define BRAINFUCK_NORMAL_MODE		0x00000000
#define BRAINFUCK_RUNTIME_CHECK		0x00000001
#define BRAINFUCK_DEBUG_MODE		0x00000002
#define BRAINFUCK_DUMP_MODE			0x00000004
#define BRAINFUCK_EXTENT_API		0x00000008
#define BRAINFUCK_BREAKPOINT		0x00000010

/* brainfuck destroy option*/
#define BRAINFUCK_FREE_MEMORY		0x00000001
#define BRAINFUCK_FREE_STACK		0x00000002
#define BRAINFUCK_FREE_CODE			0x00000010
#define BRAINFUCK_FREE_INPUT		0x00000020
#define BRAINFUCK_FREE_OUTPUT		0x00000040
#define BRAINFUCK_FREE_MEMORIES		( BRAINFUCK_FREE_MEMORY | BRAINFUCK_FREE_STACK )
#define BRAINFUCK_FREE_FILES		( BRAINFUCK_FREE_CODE | BRAINFUCK_FREE_INPUT | BRAINFUCK_FREE_OUTPUT )
#define BRAINFUCK_FREE_ALL			( BRAINFUCK_FREE_MEMORIES | BRAINFUCK_FREE_FILES )

#define BRAINFUCK_BREAKPOINT_STR	"#"
#define BRAINFUCK_BREAKPOINT_CHAR	'#'
#define BRAINFUCK_CALLAPI_STR		"!"
#define BRAINFUCK_CALLAPI_CHAR		'!'

#define BRAINFUCK_ERROR_BUFFER_SIZE				512

#define BRAINFUCK_API_CALL_MODE					0
#define BRAINFUCK_API_PROTECT_MODE				1
#define BRAINFUCK_API_PARAMSIZE_UNLIMITED		-1
#define BRAINFUCK_API_NO_PARAM					0

#define BRAINFUCK_SUCCESS						0
#define BRAINFUCK_ERROR_NULL_POINTER_REF		-1
#define BRAINFUCK_ERROR_STACK_ALLOC_FAILED		-2
#define BRAINFUCK_ERROR_MEMORY_ALLOC_FAILED		-3
#define BRAINFUCK_ERROR_LOOP_STACK_OVERFLOW		-4
#define BRAINFUCK_ERROR_MEMORY_OVERFLOW			-5
#define BRAINFUCK_ERROR_PUSH_LOOP_STACK			-6
#define BRAINFUCK_ERROR_POP_LOOP_STACK			-7
#define BRAINFUCK_ERROR_OPEN_FILE_FAILED		-8
#define BRAINFUCK_ERROR_CLOSE_FILE_FAILED		-9
#define BRAINFUCK_ERROR_BREAKPOINT_REACH		-10
#define BRAINFUCK_ERROR_PROTECT_UNLIMITED		-11

#define BRAINFUCK_API_RETURN_NO_VALUE			-1
#define BRAINFUCK_API_RETURN_VALUE( n )			(int)( n )

typedef unsigned char bf_byte;
typedef struct brainfuck_vminstance 
	brainfuck_vminstance, *brainfuck_vm;

typedef int (*brainfuck_api)(
	brainfuck_vm	vm,
	bf_byte			api_id,
	bf_byte*		param,
	int				param_len
);

typedef struct brainfuck_api_s {
	brainfuck_api	api;
	int				param_len;
	int				mode;
} brainfuck_api_s;

#define BRAINFUCK_API_DECLARE( api_name )		\
	int api_name( brainfuck_vm vm, bf_byte api_id, bf_byte* param, int param_len )

struct brainfuck_vminstance
{
	int						modes;
	bf_byte*				memory;
	int						memory_size;
	fpos_t*					stack;
	int						stack_size;
	/* registers for brainfuck interpreter */
	int						ip;		/* instruction pointer */
	int						sp;		/* stack pointer */
	int						bc;		/* bracket counter */
	int						pc;		/* program counter */
	bf_byte*				ptr;

	FILE*					code;
	FILE*					input;
	FILE*					output;
	brainfuck_api_s			apis[256];
};


/*	Create a new handle of a brainfuck interpreter instance. On success,
	a handle of interpreter instance returned. Return NULL when failed.
 */
brainfuck_vm brainfuck_build( void );

/*	Set brainfuck virtual machine run mode. Following modes can be set: BRAINFUCK_NORMAL_MODE,
	BRAINFUCK_RUNTIME_CHECK, BRAINFUCK_DEBUG_MODE, BRAINFUCK_DUMP_MODE, BRAINFUCK_EXTENT_API,
	BRAINFUCK_BREAKPOINT.
 */
int brainfuck_setmode( brainfuck_vm vm, int modes );

/*	Allocate memory of interpreter, memory_size in bytes.
 */
int brainfuck_alloc_memory( brainfuck_vm vm, int memory_size );

/*	Allocate loop stack of interpreter, stack_size is depth for stack.
 */
int brainfuck_alloc_stack( brainfuck_vm vm, int stack_size );

/*	Free memory of interpreter.
 */
void brainfuck_free_memory( brainfuck_vm vm );

/*	Free loop stack of interpreter.
 */
void brainfuck_free_stack( brainfuck_vm vm );

/*	Free resources of interpreter, options specified which to free.
 */
int brainfuck_destroy( brainfuck_vm vm, int options );

/*	Initialize the memory and loop stack of interpreter with allocated memory space.
	When memory(or stack) is NULL and memory_size(or stack_size) is a positive number,
	a space with specified size will be allocated for interpreter.
 */
int brainfuck_init( brainfuck_vm vm, bf_byte* memory, int memory_size, fpos_t* stack, int stack_size );

/*	Redirect input stream and output stream to specified file or network stream.
	Set NULL point for input(or output) stream when want take no modify for it.
 */
void brainfuck_ioredirect( brainfuck_vm vm, FILE* input, FILE* output );

/*	Load code for interpreter.
 */
void brainfuck_load( brainfuck_vm vm, FILE* code );

/*	Load code from file for interpreter.
 */
int brainfuck_loadfile( brainfuck_vm vm, const char* filename );

/*	Close code file.
 */
int brainfuck_closefile( brainfuck_vm vm );

/*	Initialize registers of interpreter.
 */
int brainfuck_start( brainfuck_vm vm );

/*	Execute codes
 */
int brainfuck_run( brainfuck_vm vm );

/*	Execute next instruction
 */
int brainfuck_run_step( brainfuck_vm vm );

/*	A default API for api calling.
 */
int brainfuck_default_api( brainfuck_vm vm, bf_byte callback_id, bf_byte* param, int param_len );

/*	Register a API for brainfuck interpreter.
 */
int brainfuck_api_register( brainfuck_vm vm, unsigned char call_id, brainfuck_api api, int param_len, int mode );

/*	Call brainfuck API
 */
int brainfuck_api_call( brainfuck_vm vm, unsigned char call_id );

/*	Do brainfuck runtime check for memory and stack
 */
int brainfuck_runtime_check( brainfuck_vm vm );

/*	Print debug info on screen
 */
void brainfuck_debug_print( brainfuck_vm vm );

/*	Translate error code to error message.
 */
int brainfuck_error_message( int errcode, char* errbuf );

#endif
